
package common;

import common.packet.Packet;
import common.packet.PacketHandler;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * RHL.
 * Implements a loop which reads a Packet from the client socket and calls a
 * handler to handle this kind of packet. If there's no handler for the packet
 * type received, the packet is discarded.
 *
 * @author xissburg
 */
public class ReceiveHandleLoop implements Runnable
{
    /**
     * The client this receive-handle-loop handles packets for.
     */
    private final Client client;

    /**
     * Currently installed packet handlers.
     */
    private final Map<Packet.Code, PacketHandler> handlers;

    /**
     * Optional default packet handler. If there is no packet handler in the
     * <tt>handlers</tt> map for a packet to be handled and this field is not null,
     * this default handler is called to handle the packet.
     */
    private PacketHandler defaultHandler;

    private ErrorHandler errorHandler;

    /**
     * Its run method is called when the main while loop terminates. The end of
     * the main loop means the client is probably dead, then its usually needed
     * to do some stuff like signal the other players about the event.
     */
    private Runnable finishRunnable;

    /**
     * Determines whether the loop should stop. A clean way to stop the main loop
     * is to set this to true.
     */
    private volatile boolean stop;

    public ReceiveHandleLoop(Client client)
    {
        this.client = client;
        handlers = new ConcurrentHashMap<Packet.Code, PacketHandler>();
        defaultHandler = null;
        errorHandler = null;
        finishRunnable = null;
        stop = false;
    }

    /**
     * Adds a new packet handler to the handlers map. If there is already a
     * handler for that packet type it is replaced by this new one. Since this
     * method may be called from other threads the access to the <tt>handlers</tt>
     * map is synchronized.
     *
     * @param handler
     */
    public void addPacketHandler(PacketHandler handler)
    {
        handlers.put(handler.getPacketCode(), handler);
    }

    public void removePacketHandler(Packet.Code code)
    {
        handlers.remove(code);
    }

    public PacketHandler getDefaultHandler() {
        return defaultHandler;
    }

    //Not thread safe wrt handle()
    public void setDefaultHandler(PacketHandler defaultHandler) {
        this.defaultHandler = defaultHandler;
    }

    public ErrorHandler getErrorHandler() {
        return errorHandler;
    }

    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    public void setFinishRunnable(Runnable finishRunnable) {
        this.finishRunnable = finishRunnable;
    }

    public Client getClient() {
        return client;
    }

    public void stop() throws IOException
    {
        stop = true;
        client.closeSocket();//close the socket to unblock the run loop
    }

    public void handle(Packet packet)
    {
        PacketHandler handler = null;

        handler = handlers.get(packet.getCode());

        if(handler != null)
            handler.handle(packet);
        else if(defaultHandler != null)
            defaultHandler.handle(packet);
    }

    public void run()
    {
        while(!stop)
        {
            Packet packet = null;
            boolean ok = true;

            try {
                packet = client.read();
            }
            catch (SocketTimeoutException ex) {
                if(errorHandler != null && stop == false)
                    ok = errorHandler.onSocketTimeoutException(ex);
                else
                    ok = false;
            }
            catch (IOException ex) {
                if(errorHandler != null && stop == false)
                    ok = errorHandler.onIOException(ex);
                else
                    ok = false;
            }
            finally {
                
                if(ok == false)
                {
                    try {
                        stop();
                    }
                    catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    finally {
                        break;
                    }
                }

                if(stop)
                    break;
            }

            if(packet != null)
                handle(packet);
        }

        if(finishRunnable != null)
            finishRunnable.run();
    }

    /**
     * If an instance of an implementation of this interface is set in a
     * ReceiveHandleLoop instance, its methods are called if the corresponding
     * exception is thrown while reading from the socket. Its methods return
     * whether the receive-handle loop should continue or terminate.
     */
    public static interface ErrorHandler
    {
        boolean onSocketTimeoutException(SocketTimeoutException ex);
        boolean onIOException(IOException ex);
    }
}
